﻿using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using ERDDiagram.CrmSdk;
using ERDDiagram.DataModel;
using ERDDiagram.helpers;
using ERDDiagram.ViewModel;
using System.Threading;


namespace ERDDiagram.Model
{
    public class DataService : IDataService
    {
        public void GetData(Action<DataItem, Exception> callback)
        {
            // Use this to connect to the actual data service

            var item = new DataItem("CRM 2011 ERD Designer");
            callback(item, null);
        }
        #region private variables

        private ObservableCollection<EntityClass> _entities;
        private ObservableCollection<SolutionClass> _solutions;
        private EntityClass _entity;
        private String _diagramdata;

        private Action<EntityClass, Exception> _callbackEntities;
        private Action<EntityClass, Exception> _callbackEntitiesBySolution;
        private Action<EntityClass, Exception> _callback;
        private Action<SolutionClass, Exception> _callbacksolution;
        private Action<DataItem, Exception> _callback2;
        #endregion


        #region contructors

        /// <summary>
        /// Constructor
        /// </summary>
        public DataService()
        {
            _entities = new ObservableCollection<EntityClass>();
            _entity = new EntityClass("", "", false, Guid.Empty);
        }

        #endregion


        #region IMetadataReader Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="exceptionMessages"></param>
        /// <param name="exceptionHandler"></param>
        /// <returns></returns>
        public ObservableCollection<EntityClass> GetEntities(Action<EntityClass, Exception> callback)
        {
            try
            {

                _entities = new ObservableCollection<EntityClass>();

                Uri serverUrl = new Uri(ClientScript.GetServerUrl());
                _callbackEntities = callback;
                ExecuteRetrieveEntities(serverUrl);

                return _entities;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }

        public EntityClass GetEntity(String LogicalName, bool IncludeRelationships, String SchemaName, Action<EntityClass, Exception> callback)
        {
            try
            {
                if (!String.IsNullOrEmpty(SchemaName))
                {
                    _entity = new EntityClass(SchemaName, "", false, Guid.Empty);
                }
                else
                {
                    _entity = new EntityClass("", LogicalName, false, Guid.Empty);
                }


                Uri serverUrl = new Uri(ClientScript.GetServerUrl());
                _callback = callback;
                ExecuteRetrieveEntity(LogicalName, IncludeRelationships, serverUrl, SchemaName);

                return _entity;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }
        }





        #endregion


        #region private methods


        /// <summary>
        /// Retrieves 
        /// </summary>
        /// <param name=ServerUrl">ServerUrl</param>
        private void ExecuteRetrieveEntities(Uri ServerUrl)
        {
            try
            {
                OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(ServerUrl);

                OrganizationRequest request = new OrganizationRequest();
                request.RequestName = "RetrieveAllEntities";
                request.Parameters = new ParameterCollection();
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("EntityFilters", EntityFilters.Entity));
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("RetrieveAsIfPublished", false));
                client.ExecuteCompleted += new EventHandler<CrmSdk.ExecuteCompletedEventArgs>(ExecuteRetrieveEntities_ExecuteCompleted);

                client.ExecuteAsync(request);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="response"></param>
        private void ExecuteRetrieveEntities_ExecuteCompleted(object sender, CrmSdk.ExecuteCompletedEventArgs response)
        {
            try
            {

                ObservableCollection<EntityMetadata> data = (ObservableCollection<EntityMetadata>)response.Result.Results[0].Value;
                _entities.Add(new EntityClass("(New...)", "new", true, Guid.Empty));
                var entities = from a in data
                               where a.DisplayName.LocalizedLabels.Count > 0
                               orderby a.DisplayName.LocalizedLabels[0].Label
                               select new { SchemaName = a.DisplayName.LocalizedLabels[0].Label, LogicalName = a.LogicalName, IsCustomEntity = a.IsCustomEntity, EntityId = a.MetadataId, IsManaged = a.IsManaged };

                foreach (var entity in entities)
                {
                    EntityClass ee = new EntityClass(entity.SchemaName, entity.LogicalName, entity.IsCustomEntity, entity.EntityId.Value);
                    if (entity.IsManaged != null)
                    {
                        ee.IsManagedEntity = (bool)entity.IsManaged;
                    }
                    _entities.Add(ee);
                }
                if (_callbackEntities != null)
                {
                    _callbackEntities(_entity, null);
                }

            }

            catch (FaultException<OrganizationServiceFault> se)
            {
                throw se;
            }
        }


        /// <summary>
        /// Retrieves 
        /// </summary>
        /// <param name=ServerUrl">ServerUrl</param>
        private void ExecuteRetrieveEntity(String EntityLogicalName, bool IncludeRelationships, Uri ServerUrl, String SchemaName)
        {
            try
            {
                OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(ServerUrl);

                OrganizationRequest request = new OrganizationRequest();
                request.RequestName = "RetrieveEntity";
                request.Parameters = new ParameterCollection();
                if (!String.IsNullOrEmpty(SchemaName))
                {
                    request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("SchemaName", SchemaName));
                }
                else
                {
                    request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("LogicalName", EntityLogicalName));

                }
                if (IncludeRelationships)
                    request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("EntityFilters", EntityFilters.Attributes | EntityFilters.Relationships));
                else
                    request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("EntityFilters", EntityFilters.Attributes));

                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("MetadataId", new Guid("00000000-0000-0000-0000-000000000000")));
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("RetrieveAsIfPublished", false));
                client.ExecuteCompleted += new EventHandler<CrmSdk.ExecuteCompletedEventArgs>(ExecuteRetrieveEntity_ExecuteCompleted);

                client.ExecuteAsync(request);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }

        /// <summary>
        /// Handles the post back for retrieveing an entity
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="response"></param>
        private void ExecuteRetrieveEntity_ExecuteCompleted(object sender, CrmSdk.ExecuteCompletedEventArgs response)
        {
            try
            {
                EntityClass entityTmp = _entity;
                if (response.Error == null)
                {
                    EntityMetadata data = (EntityMetadata)response.Result.Results[0].Value;
                    entityTmp.LogicalName = data.LogicalName;
                    entityTmp.SchemaNameSet = data.DisplayName.LocalizedLabels[0].Label;
                    entityTmp.IsManagedEntity = (bool)data.IsManaged;
                    entityTmp.IsCustomEntity = (bool)data.IsCustomEntity;
                    entityTmp.EntityId = (Guid)data.MetadataId;

                    if (data.Attributes != null)
                    {



                        var attributes = from a in data.Attributes
                                         orderby a.SchemaName
                                         where a.AttributeOf == null
                                         select a;

                        foreach (var attribute in attributes)
                        {
                            AttributeClass newAttr = new AttributeClass(attribute, data.MetadataId.Value);
                            if (newAttr.AttributeType.Value == AttributeTypeCode.Lookup)
                            {
                                CrmSdk.LookupAttributeMetadata lookup = (CrmSdk.LookupAttributeMetadata)(newAttr.Metadata);

                                if (lookup.Targets.Count > 0)
                                {
                                    newAttr.TargetRecordType = lookup.Targets[0];
                                }
                            }
                            entityTmp.Attributes.Add(newAttr);
                        }


                    }

                    if (data.OneToManyRelationships != null)
                    {
                        var oneToManys = from a in data.OneToManyRelationships
                                         orderby a.SchemaName
                                         where a.IsValidForAdvancedFind.Value.Equals(true)
                                         select new { SchemaName = a.SchemaName, EntityLogicalName = a.ReferencingEntity, EntityRelationshipId = (Guid)a.MetadataId, PrimaryEntity = a.ReferencedAttribute };

                        foreach (var oneToMany in oneToManys)
                        {
                            entityTmp.Relationships_OneToMany.Add(new RelationshipClass(oneToMany.SchemaName, oneToMany.EntityLogicalName, oneToMany.EntityRelationshipId, oneToMany.PrimaryEntity));

                        }
                    }

                    if (data.ManyToOneRelationships != null)
                    {
                        var manyToOnes = from a in data.ManyToOneRelationships
                                         orderby a.SchemaName
                                         where a.IsValidForAdvancedFind.Value.Equals(true)
                                         select new { SchemaName = a.SchemaName, EntityLogicalName = a.ReferencedEntity, EntityRelationshipId = (Guid)a.MetadataId, PrimaryEntity = a.ReferencedAttribute };

                        foreach (var manyToOne in manyToOnes)
                        {
                            entityTmp.Relationships_ManyToOne.Add(new RelationshipClass(manyToOne.SchemaName, manyToOne.EntityLogicalName, manyToOne.EntityRelationshipId, manyToOne.PrimaryEntity));
                        }

                    }

                    if (data.ManyToManyRelationships != null)
                    {
                        var manyToManys = from a in data.ManyToManyRelationships
                                          orderby a.SchemaName
                                          where a.IsValidForAdvancedFind.Value.Equals(true)
                                          select new { SchemaName = a.SchemaName, EntityLogicalName = a.Entity2LogicalName, EntityRelationshipId = (Guid)a.MetadataId, PrimaryEntity  = a.Entity1LogicalName};

                        foreach (var manyToMany in manyToManys)
                        {
                            entityTmp.Relationships_ManyToMany.Add(new RelationshipClass(manyToMany.SchemaName, manyToMany.EntityLogicalName, manyToMany.EntityRelationshipId, manyToMany.PrimaryEntity));
                        }
                    }
                }
                _entity = entityTmp;
                if (_callback != null)
                {
                    _callback(_entity, null);
                }

            }

            catch (FaultException<OrganizationServiceFault> se)
            {
                throw se;
            }
        }


        #endregion

        public string GetDiagramData(string DiagramID, Action<DataItem, Exception> callback)
        {
            Uri serverUrl = new Uri(ClientScript.GetServerUrl());
            OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(serverUrl);

            ColumnSet cols = new ColumnSet();
            cols.Columns = new ObservableCollection<string>();
            cols.Columns.Add("remcod_data");

            _callback2 = callback;
            client.RetrieveCompleted += new EventHandler<RetrieveCompletedEventArgs>(client_RetrieveCompleted);
            client.RetrieveAsync("remcod_erddiagram", new Guid(DiagramID), cols);
            return "";
        }

        void client_RetrieveCompleted(object sender, RetrieveCompletedEventArgs e)
        {
            if (_callback2 != null)
            {
                _diagramdata = "";
                if (e.Result != null && e.Result.Attributes.Count > 1 && e.Result.Attributes[0] != null && e.Result.Attributes[0].Value != null)
                {
                    _diagramdata = e.Result.Attributes[0].Value.ToString();
                }
                DataItem mda = new DataItem(_diagramdata);

                _callback2(mda, null);
            }
        }
        public void SaveDiagramData(string DiagramID, string Data, Action<EntityClass, Exception> callback)
        {

            Uri serverUrl = new Uri(ClientScript.GetServerUrl());
            OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(serverUrl);
            Entity myDiagramentity = new Entity();
            _callback = callback;
            myDiagramentity.Id = new Guid(DiagramID);
            myDiagramentity.LogicalName = "remcod_erddiagram";
            myDiagramentity.Attributes = new AttributeCollection();
            myDiagramentity.Attributes.Add(new KeyValuePair<string, object>("remcod_data", Data));
            client.UpdateCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_UpdateCompleted);
            client.UpdateAsync(myDiagramentity);
        }
        private void client_UpdateCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs response)
        {
            if (_callback != null)
            {
                _callback(_entity, null);
            }

        }


        public string GetDefaultSolutionId()
        {
            throw new NotImplementedException();
        }


        public void DeleteEntity(string EntityID, Action<EntityClass, Exception> callback)
        {
            Uri serverUrl = new Uri(ClientScript.GetServerUrl());
            OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(serverUrl);
            _callback = callback;

            OrganizationRequest request = new OrganizationRequest();
            request.RequestName = "DeleteEntity";
            request.Parameters = new ParameterCollection();
            request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("LogicalName", EntityID));
            client.ExecuteCompleted += new EventHandler<CrmSdk.ExecuteCompletedEventArgs>(client_ExecuteCompleted);
            client.ExecuteAsync(request);
        }

        void client_ExecuteCompleted(object sender, CrmSdk.ExecuteCompletedEventArgs response)
        {
            if (_callback != null)
            {
                _callback(_entity, null);
            }
        }


        public void DeleteField(string entityName, string attribute, Action<EntityClass, Exception> callback)
        {
            Uri serverUrl = new Uri(ClientScript.GetServerUrl());
            OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(serverUrl);
            _callback = callback;

            OrganizationRequest request = new OrganizationRequest();
            request.RequestName = "DeleteAttribute";
            request.Parameters = new ParameterCollection();
            request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("EntityLogicalName", entityName));
            request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("LogicalName", attribute));
            client.ExecuteCompleted += new EventHandler<CrmSdk.ExecuteCompletedEventArgs>(client_ExecuteCompleted);
            client.ExecuteAsync(request);

        }


        public ObservableCollection<SolutionClass> GetSolutions(Action<SolutionClass, Exception> callback)
        {
            try
            {

                _solutions = new ObservableCollection<SolutionClass>();

                Uri serverUrl = new Uri(ClientScript.GetServerUrl());
                _callbacksolution = callback;
                ExecuteRetrieveSolutions(serverUrl);

                return _solutions;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Retrieves 
        /// </summary>
        /// <param name=ServerUrl">ServerUrl</param>
        private void ExecuteRetrieveSolutions(Uri ServerUrl)
        {
            try
            {
                OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(ServerUrl);

                //Create Query Expression.
                QueryExpression query = new QueryExpression()
                {
                    EntityName = "solution",
                    ColumnSet = new ColumnSet()
                    {
                        Columns = new ObservableCollection<string>() { 
                            "solutionid","friendlyname"}
                    }
                };
                client.RetrieveMultipleCompleted += new EventHandler<CrmSdk.RetrieveMultipleCompletedEventArgs>(RetrieveSolutions_Completed);
                client.RetrieveMultipleAsync(query);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="response"></param>
        private void RetrieveSolutions_Completed(object sender, CrmSdk.RetrieveMultipleCompletedEventArgs response)
        {
            try
            {
                _solutions.Add(new SolutionClass(Guid.Empty, "(New...)"));
                foreach (Entity entity in response.Result.Entities)
                {
                    if (entity.Attributes[0].Value.ToString() != "fd140aad-4df4-11dd-bd17-0019b9312238" &&
                        entity.Attributes[0].Value.ToString() != "fd140aaf-4df4-11dd-bd17-0019b9312238" &&
                        entity.Attributes[0].Value.ToString() != "fd140aae-4df4-11dd-bd17-0019b9312238" &&
                        entity.Attributes[0].Value.ToString() != "25a01723-9f63-4449-a3e0-046cc23a2902")
                    {
                        SolutionClass ee = new SolutionClass(new Guid(entity.Attributes[0].Value.ToString()),
                                entity.Attributes[1].Value.ToString());
                        _solutions.Add(ee);
                    }
                }
                if (_callbacksolution != null)
                {
                    _callbacksolution(null, null);
                }

            }

            catch (FaultException<OrganizationServiceFault> se)
            {
                throw se;
            }
        }



        public ObservableCollection<EntityClass> GetEntitiesBySolution(Guid solutionID, Action<EntityClass, Exception> callback)
        {
            try
            {
                _entities = new ObservableCollection<EntityClass>();
                Uri serverUrl = new Uri(ClientScript.GetServerUrl());
                _callbackEntitiesBySolution = callback;
                ExecuteRetrieveEntitiesBySolution(solutionID, serverUrl);
                return _entities;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Retrieves 
        /// </summary>
        /// <param name=ServerUrl">ServerUrl</param>
        private void ExecuteRetrieveEntitiesBySolution(Guid solutionID, Uri ServerUrl)
        {
            try
            {
                OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(ServerUrl);

                //Create Query Expression.
                QueryExpression query = new QueryExpression()
                {
                    EntityName = "solutioncomponent",
                    ColumnSet = new ColumnSet()
                    {
                        Columns = new ObservableCollection<string>() { 
                            "solutioncomponentid", "objectid"}
                    },
                    Criteria = new FilterExpression()
                    {
                        FilterOperator = LogicalOperator.And,
                        Conditions = new ObservableCollection<ConditionExpression>() {
                                new ConditionExpression() {
                                    AttributeName = "solutionid",
                                    Operator = ConditionOperator.Equal,
                                    Values = new ObservableCollection<object>() { solutionID }
                                },
                                new ConditionExpression() {
                                    AttributeName = "componenttype",
                                    Operator = ConditionOperator.Equal,
                                    Values = new ObservableCollection<object>() { 1 } // 1 = Entity component Type
                                }                                   
                             }
                    },
                };
                client.RetrieveMultipleCompleted += new EventHandler<CrmSdk.RetrieveMultipleCompletedEventArgs>(RetrieveEntitiesBySolution_Completed);
                client.RetrieveMultipleAsync(query);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="response"></param>
        private void RetrieveEntitiesBySolution_Completed(object sender, CrmSdk.RetrieveMultipleCompletedEventArgs response)
        {
            try
            {
                foreach (Entity entity in response.Result.Entities)
                {
                    EntityClass ee = new EntityClass();
                    ee.EntityId = (Guid)entity.Attributes[1].Value;
                    _entities.Add(ee);
                }
                if (_callbackEntitiesBySolution != null)
                {
                    _callbackEntitiesBySolution(null, null);
                }

            }

            catch (FaultException<OrganizationServiceFault> se)
            {
                throw se;
            }
        }



        public EntityClass GetEntityById(Guid entityId, Action<EntityClass, Exception> callback)
        {
            try
            {

                Uri serverUrl = new Uri(ClientScript.GetServerUrl());
                _callback = callback;
                ExecuteRetrieveEntityById(entityId, serverUrl);

                return _entity;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Retrieves 
        /// </summary>
        /// <param name=ServerUrl">ServerUrl</param>
        private void ExecuteRetrieveEntityById(Guid entityId, Uri ServerUrl)
        {
            try
            {
                OrganizationServiceClient client = (OrganizationServiceClient)WCFHelper.GetConnection(ServerUrl);

                OrganizationRequest request = new OrganizationRequest();
                request.RequestName = "RetrieveEntity";
                request.Parameters = new ParameterCollection();
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("EntityFilters", EntityFilters.Attributes | EntityFilters.Relationships));
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("MetadataId", entityId));
                request.Parameters.Add(new ERDDiagram.DataModel.KeyValuePair<string, object>("RetrieveAsIfPublished", false));
                client.ExecuteCompleted += new EventHandler<CrmSdk.ExecuteCompletedEventArgs>(ExecuteRetrieveEntity_ExecuteCompleted);

                client.ExecuteAsync(request);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw ex;
            }

        }




    }
}